package io.gitee.zhangbinhub.admin.oauth.component

import io.gitee.zhangbinhub.acp.cloud.resource.server.conf.AcpCloudResourceServerConfiguration
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.admin.oauth.constant.OauthConstant
import io.gitee.zhangbinhub.admin.oauth.service.ApplicationService
import jakarta.annotation.PostConstruct
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.oauth2.core.AuthorizationGrantType
import org.springframework.security.oauth2.core.ClientAuthenticationMethod
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings
import org.springframework.stereotype.Component
import org.springframework.util.Assert
import org.springframework.util.StringUtils
import java.time.Duration
import java.util.concurrent.ConcurrentHashMap

/**
 * @author zhangbin by 11/04/2018 15:21
 * @since JDK 11
 */
@Component
class AuthClientService @Autowired
constructor(
    private val applicationService: ApplicationService,
    private val acpCloudResourceServerConfiguration: AcpCloudResourceServerConfiguration
) : RegisteredClientRepository {
    private val idRegistrationMap: ConcurrentHashMap<String, RegisteredClient> = ConcurrentHashMap()
    private val clientIdRegistrationMap: ConcurrentHashMap<String, RegisteredClient> = ConcurrentHashMap()

    /**
     * 初始化客户端信息
     */
    @PostConstruct
    fun loadClientInfo() = applicationService.getAppList().let { applicationList ->
        idRegistrationMap.clear()
        clientIdRegistrationMap.clear()
        applicationList.forEach { application ->
            RegisteredClient.withId(application.id)
                .clientId(application.id)
                .clientSecret(application.secret)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizationGrantType(AuthorizationGrantType(OauthConstant.granterUserPassword))
                .scopes { scopes ->
                    application.scope?.apply {
                        if (!CommonTools.isNullStr(this)) {
                            this.split(",").forEach { appScope -> scopes.add(appScope) }
                        }
                    }
                }
                .tokenSettings(
                    TokenSettings.builder()
                        .accessTokenFormat(OAuth2TokenFormat.REFERENCE)
                        .reuseRefreshTokens(true)
                        .accessTokenTimeToLive(Duration.ofSeconds(application.accessTokenValiditySeconds.toLong()))
                        .refreshTokenTimeToLive(Duration.ofSeconds(application.refreshTokenValiditySeconds.toLong()))
                        .build()
                )
                .build().apply {
                    save(this)
                }
        }
        val registeredClientInner = RegisteredClient.withId("acpCloudInnerClient")
            .clientId(acpCloudResourceServerConfiguration.clientId)
            .clientSecret(acpCloudResourceServerConfiguration.clientSecret)
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .scopes { scopes -> scopes.add("INNER") }
            .tokenSettings(
                TokenSettings.builder()
                    .accessTokenFormat(OAuth2TokenFormat.REFERENCE)
                    .reuseRefreshTokens(true)
                    .accessTokenTimeToLive(Duration.ofSeconds(600))
                    .refreshTokenTimeToLive(Duration.ofSeconds(86400)).build()
            )
            .build()
        save(registeredClientInner)
    }

    override fun save(registeredClient: RegisteredClient) {
        Assert.notNull(registeredClient, "registeredClient cannot be null")
        assertUniqueIdentifiers(registeredClient, idRegistrationMap)
        idRegistrationMap[registeredClient.id] = registeredClient
        clientIdRegistrationMap[registeredClient.clientId] = registeredClient
    }

    override fun findById(id: String): RegisteredClient? {
        Assert.hasText(id, "id cannot be empty")
        return idRegistrationMap[id]
    }

    override fun findByClientId(clientId: String): RegisteredClient? {
        Assert.hasText(clientId, "clientId cannot be empty")
        return clientIdRegistrationMap[clientId]
    }

    private fun assertUniqueIdentifiers(
        registeredClient: RegisteredClient,
        registrations: Map<String, RegisteredClient>
    ) {
        registrations.values.forEach { registration ->
            require(registeredClient.id != registration.id) {
                "Registered client must be unique. " +
                        "Found duplicate identifier: " + registeredClient.id
            }
            require(registeredClient.clientId != registration.clientId) {
                "Registered client must be unique. " +
                        "Found duplicate client identifier: " + registeredClient.clientId
            }
            require(StringUtils.hasText(registeredClient.clientSecret) && registeredClient.clientSecret != registration.clientSecret) {
                "Registered client must be unique. " +
                        "Found duplicate client secret for identifier: " + registeredClient.id
            }
        }
    }
}
